import { mergeAttributes, Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-3'

import NodeView from './node-view.vue'

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    insertComment: {
      insertComment: (options: any) => ReturnType
    }
    updateComment: {
      updateComment: (id: string, options: any) => ReturnType
    }
    deleteComment: {
      deleteComment: (id: string) => ReturnType
    }
  }
}

export default Node.create({
  name: 'comment',
  group: 'inline',
  inline: true,
  atom: true,

  addAttributes() {
    return {
      id: { 
        default: null,
        parseHTML: element => element.getAttribute('data-comment-id'),
        renderHTML: attributes => {
          if (!attributes.id) {
            return {}
          }
          return {
            'data-comment-id': attributes.id,
          }
        },
      },
      author: { 
        default: '匿名用户',
        parseHTML: element => element.getAttribute('data-author'),
        renderHTML: attributes => {
          if (!attributes.author) {
            return {}
          }
          return {
            'data-author': attributes.author,
          }
        },
      },
      content: { 
        default: '',
        parseHTML: element => element.getAttribute('data-content'),
        renderHTML: attributes => {
          if (!attributes.content) {
            return {}
          }
          return {
            'data-content': attributes.content,
          }
        },
      },
      createdAt: { 
        default: () => new Date().toISOString(),
        parseHTML: element => element.getAttribute('data-created-at'),
        renderHTML: attributes => {
          if (!attributes.createdAt) {
            return {}
          }
          return {
            'data-created-at': attributes.createdAt,
          }
        },
      },
      updatedAt: { 
        default: null,
        parseHTML: element => element.getAttribute('data-updated-at'),
        renderHTML: attributes => {
          if (!attributes.updatedAt) {
            return {}
          }
          return {
            'data-updated-at': attributes.updatedAt,
          }
        },
      },
      resolved: { 
        default: false,
        parseHTML: element => element.getAttribute('data-resolved') === 'true',
        renderHTML: attributes => {
          return {
            'data-resolved': attributes.resolved.toString(),
          }
        },
      },
    }
  },

  parseHTML() {
    return [{ tag: 'span[data-type="comment"]' }]
  },

  renderHTML({ HTMLAttributes }) {
    return [
      'span',
      mergeAttributes(HTMLAttributes, { 
        'data-type': 'comment',
        'class': 'umo-comment',
      }),
      '💬',
    ]
  },

  addNodeView() {
    return VueNodeViewRenderer(NodeView)
  },

  addCommands() {
    return {
      insertComment:
        (options) =>
        ({ chain }) => {
          const id = options.id || `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
          return chain()
            .insertContent({
              type: this.name,
              attrs: {
                ...options,
                id,
                createdAt: new Date().toISOString(),
              },
            })
            .run()
        },
      updateComment:
        (id, options) =>
        ({ tr, dispatch }) => {
          const { doc } = tr
          let commentNode: any = null
          let commentPos: number = -1

          doc.descendants((node, pos) => {
            if (node.type.name === this.name && node.attrs.id === id) {
              commentNode = node
              commentPos = pos
              return false
            }
          })

          if (commentNode && commentPos >= 0) {
            const updatedAttrs = {
              ...commentNode.attrs,
              ...options,
              updatedAt: new Date().toISOString(),
            }
            
            if (dispatch) {
              dispatch(
                tr.setNodeMarkup(commentPos, undefined, updatedAttrs)
              )
            }
            return true
          }
          
          return false
        },
      deleteComment:
        (id) =>
        ({ tr, dispatch }) => {
          const { doc } = tr
          let commentPos: number = -1

          doc.descendants((node, pos) => {
            if (node.type.name === this.name && node.attrs.id === id) {
              commentPos = pos
              return false
            }
          })

          if (commentPos >= 0) {
            if (dispatch) {
              dispatch(tr.delete(commentPos, commentPos + 1))
            }
            return true
          }
          
          return false
        },
    }
  },
})